<?php
/*
 * Copyright (C) 2004-2011 wortzwei GmbH <info@wortzwei.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @since 14.01.2011
 * @author Dirk Dittmar
 */

require_once( dirname(dirname(__FILE__)) . '/Apache/Solr/Service.php' );

/**
 * Service for Solr
 * @author Dirk Dittmar
 */
class SolrSearch_SolrService {

	private $apacheSolrService;

	private $additionalParameters = array(
		'fl' => 'id,score',
	);

	/**
	 * Constructor. All parameters are optional and will take on default values
	 * if not specified.
	 *
	 * @param string $host
	 * @param string $port
	 * @param string $path
	 */
	public function __construct($host = 'localhost', $port = 8080, $path = '/solr/') {
		$this->apacheSolrService = new Apache_Solr_Service($host, $port, $path);
	}

	/**
	 * Search for Issue-Id and Score
	 * @param string $query
	 * @param int $page_number
	 * @param int $num_rows
	 * @param int $curProjectId
	 */
	public function search($query, $page_number = 1, $num_rows = 10, $curProjectId = null) {
		$start = $num_rows * ($page_number-1);

		$additionalParameters = $this->additionalParameters;

		if ( isset($curProjectId) && $curProjectId > 0 ) {
			$projectName = project_get_name($curProjectId);
			$additionalParameters['fq'] = $this->createProjectSearchCrit($projectName);
		} else {
			// want to see all projects? -> only the ones the actual user can see...
			$proj_names = array();
			$project_ids = current_user_get_accessible_projects(true);
			foreach ($project_ids as $project_id) {
				$proj_names[] = $this->createProjectSearchCrit(project_get_name($project_id));
				$subproject_ids = current_user_get_accessible_subprojects($project_id, true);
				foreach ($subproject_ids as $subproject_id) {
					$proj_names[] = $this->createProjectSearchCrit(project_get_name($subproject_id));
				}
			}
			$additionalParameters['fq'] = implode(' OR ', $proj_names);
		}
		return $this->apacheSolrService->search($query, $start, $num_rows, $additionalParameters);
	}

	private function createProjectSearchCrit(string $projectName) {
		return 'project:"' . $projectName . '"';
	}

	/**
	 * Send a commit command.  Will be synchronous unless both wait parameters are set to false.
	 *
	 * @param boolean $optimize Defaults to true
	 * @param boolean $waitFlush Defaults to true
	 * @param boolean $waitSearcher Defaults to true
	 * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour
	 * @return Apache_Solr_Response
	 *
	 * @throws Exception If an error occurs during the service call
	 */
	public function commit($optimize = true, $waitFlush = true, $waitSearcher = true, $timeout = 3600) {
		return $this->apacheSolrService->commit($optimize, $waitFlush, $waitSearcher, $timeout);
	}

	/**
	 * Create a delete document based on document ID
	 *
	 * @param string $id Expected to be utf-8 encoded
	 * @param boolean $fromPending
	 * @param boolean $fromCommitted
	 * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
	 * @return Apache_Solr_Response
	 *
	 * @throws Exception If an error occurs during the service call
	 */
	public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600) {
		return $this->apacheSolrService->deleteById($id, $fromPending, $fromCommitted, $timeout);
	}

	/**
	 * Send an optimize command.  Will be synchronous unless both wait parameters are set
	 * to false.
	 *
	 * @param boolean $waitFlush
	 * @param boolean $waitSearcher
	 * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception)
	 * @return Apache_Solr_Response
	 *
	 * @throws Exception If an error occurs during the service call
	 */
	public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600) {
		return $this->apacheSolrService->optimize($waitFlush, $waitSearcher, $timeout);
	}

	/**
	 * drops and recreates the index
	 * @return int the number of bugs added to the index
	 */
	public function rebuildIndex() {
		$bugsAdded = 0;

		$this->dropIndex();

		$page_number = 1;
		$per_page = 100;

		$filter_result = $this->getAllBugs($page_number, $per_page);
		$this->addBugs($filter_result);
		$bugsAdded += count($filter_result);
		while (count($filter_result) > 0) {
			$page_number++;
			$filter_result = $this->getAllBugs($page_number, $per_page);
			$this->addBugs($filter_result);
			$bugsAdded += count($filter_result);
		}

		$this->commit(true, false, false);
		$this->optimize(false, false);

		return $bugsAdded;
	}

	private function getAllBugs($page_number, $per_page) {
		$bug_table = db_get_table( 'mantis_bug_table' );
		$query = 'SELECT * FROM ' . $bug_table ;

		$qresult = db_query_bound($query, null, $per_page, ($page_number - 1) * $per_page);
		$row_count = db_num_rows( $qresult );

		$allBugs = array();
		for( $i = 0; $i < $row_count; $i++ ) {
			$row = db_fetch_array( $qresult );
			$rows[] = $row;
			$bug_data = new BugData;
			$bug_data->loadrow( $row );
			$allBugs[] = $bug_data;
		}

		return $allBugs;
	}

	/**
	 * Add an array of BugData Objects to the Index. Does not commit!
	 * @param array $arr_bug_data
	 */
	public function addBugs($arr_bug_data) {
		$doc_arr = array();
		foreach ($arr_bug_data as $bug) {
			$doc_arr[] = $this->createApacheSolrDocument($bug);
		}
		$this->apacheSolrService->addDocuments($doc_arr);
	}

	/**
	 * Add a BugData to the index. Does not commit!
	 * @param BugData $bug_data
	 */
	public function addBug($bug_data) {
		$doc = $this->createApacheSolrDocument($bug_data);
		$this->apacheSolrService->addDocument($doc);
	}

	/**
	 * Creates a Apache_Solr_Document from a BugData
	 * @param BugData $bug
	 * @return Apache_Solr_Document
	 */
	private function createApacheSolrDocument($bug) {
		$doc = new Apache_Solr_Document();

		// fill the doc
		$doc->addField("id", $bug->id);
		$doc->addField("project", project_get_name($bug->project_id));
		$doc->addField("summary", $bug->summary);
		$doc->addField("description", $bug->description);
		$doc->addField("steps_to_reproduce", $bug->steps_to_reproduce);
		$doc->addField("additional_information", $bug->additional_information);

		// fill the notes
		foreach( bugnote_get_all_visible_bugnotes( $bug->id, 'ASC', 0 ) as $bugnote ) {
			$doc->addField("note", $bugnote->note);
		}

		return $doc;
	}

	/**
	 * drops the index
	 */
	private function dropIndex() {
		$this->apacheSolrService->deleteByQuery("*:*");
		$this->commit(false);
	}

}